package picoevo.es.cmaes.cma;

import java.util.Properties;

/**
 * Read, use and modify this class source code to learn how to use the class
 * CMAEvolutionStrategy. Implements a restart strategy with increasing
 * population size (IPOP). Final termination criteria are stopFunctionValue and
 * stopEvaluations (see inner class Options). The remaining termination criteria
 * invoke a restart with double population size.
 */
public class TestCMAExample1 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		int nbRuns = 1; // restarts, re-read from properties file below
		int dim = 10; // number of variables re-read from properties below
		SolutionGeneric bestSolution = new SolutionGeneric();
		AbstractObjectiveFunction f;

		int it = 0;

		// f = new Rosen();
		// f = new Ackley(10);
		// f = new Rastrigin(1e0, 10);
		// f = new Cigar(1e3);
		// f = new RandFun();
		// f = new Sphere();
		// f = new Elli(1e3);

		f = new Sphere();

		long counteval = 0;
		int irun = 0;
		for (irun = 0; irun < nbRuns; ++irun) {
			CMAEvolutionStrategy cma = new CMAEvolutionStrategy();

			// setup initialization and options, either reading a file
			cma.loadProperties(); // reads from file cma.propertiesFileName
			Properties props = cma.getProperties();
			// props.list(System.out);
			dim = (int) Double.parseDouble(props.getProperty("dimension", Integer.toString(dim)));
			nbRuns = (int) Double.parseDouble(props.getProperty("numberOfRuns", Integer.toString(nbRuns)));

			// or using the respective setter functions and options
			// cma.setDimension(dim);
			// cma.setInitialSearchRegion(-0, 5);
			// cma.opts.stopFunctionValue = new Double(1e-7);
			// cma.opts.stopEvaluations = new Long((long) (1e2 + 3e4*dim*dim));

			// cma.parameters.setCcov(0.0); // for "CSA"

			// cma.opts.stopTolXFactor = new Double(0e-7);
			// cma.opts.stopTolX = new Double(0e-7);
			// cma.opts.lowerStdDev = new double[]{1e-4, 1e-8};
			// cma.opts.stopTolFun = new Double(1e-8);
			// cma.opts.stopTolFunHist = new Double(-1e-8);

			if (irun == 0) {
				cma.writeToDefaultFilesHeaders();
			} else {
				cma.opts.flgAppendFiles = true; // append output files
				cma.parameters.setPopulationSize((int) (cma.parameters.getPopulationSize() * Math.pow(2, irun)));
				cma.init();
				cma.counteval = counteval; // yet a real hack TODO
			}

			if (nbRuns > 1) // if multistarts also use iterations as termination
				// criterion
				cma.opts.stopIterations = new Long((long) (1e2 + 100 * dim * dim * Math.sqrt(cma.parameters.getLambda())));

			// Generation loop

			double lastCountEval = 0, waitCountEval = 0;
			double lastTime = 0, llastTime = 0;
			System.out.println(cma.printHeader());
			while (cma.stopConditions.isFalse()) {

				it++;

				double[][] pop = cma.samplePopulation(); // first index:
				// individual,
				// second index:
				// variable
				double[] fitness;

				fitness = f.valueOf(pop); // parallel evaluation of
				// pop[0]..pop[lambda-1]

				cma.updateDistribution(fitness);

				// the remainder is output
				// file output
				if (cma.stopConditions.isTrue() || cma.getCountEval() >= lastCountEval + waitCountEval) {
					cma.writeToDefaultFilesNew();
					lastCountEval = cma.getCountEval();
					waitCountEval = waitCountEval + 1;
				}
				// screen output
				if (System.currentTimeMillis() - llastTime > 20000) {
					cma.printlnCaption(System.out);
					llastTime = System.currentTimeMillis();
				}
				if (cma.stopConditions.isTrue() || cma.getCountIter() < 4 || System.currentTimeMillis() - lastTime > 2500) {
					cma.println(System.out);
					lastTime = System.currentTimeMillis();
				}
			} // generation loop

			// evaluate f.valueOf(cma.getMeanX()) here, because it might be
			// better than cma.getBestX()?
			// retain best solution found so far here!?
			if (cma.getBestSolution().getFunctionValue() < bestSolution.getFunctionValue())
				bestSolution = (SolutionGeneric) cma.getBestSolution();

			counteval = cma.getCountEval(); // keep number of f-evals
			System.out.println("Stop in cma: '" + cma.stopConditions.getStrings()[0] + "'");

			// quit loop given the following termination conditions are
			// satisfied, otherwise continue
			if (cma.stopConditions.getStrings()[0].substring(0, 11).compareTo("Evaluations") == 0 || cma.stopConditions.getStrings()[0].substring(0, 13).compareTo("FunctionValue") == 0) {
				++irun;
				break;
			}
		} // for irun < nbRuns
		if (irun > 1) {
			System.out.println(" " + (irun) + " runs conducted.");
			System.out.println(" best function value " + bestSolution.getFunctionValue() + " at evaluation " + bestSolution.getEvaluation());
		}
	} // main
} // class

class RandFun extends AbstractObjectiveFunction {
	java.util.Random rand = new java.util.Random(0);

	@Override
	public double valueOf(double[] x) {
		double res = rand.nextDouble();
		return res;
	}
}

class Sphere extends AbstractObjectiveFunction {
	@Override
	public double valueOf(double[] x) {
		double res = 0;
		for (int i = 0; i < x.length; ++i)
			res += x[i] * x[i];
		return res;
	}
}

class Cigar extends AbstractObjectiveFunction {
	Cigar() {
		this(1e3);
	}

	Cigar(double axisratio) {
		factor = axisratio * axisratio;
	}

	public double factor = 1e6;

	@Override
	public double valueOf(double[] x) {
		double res = x[0] * x[0];
		for (int i = 1; i < x.length; ++i)
			res += factor * x[i] * x[i];
		return res;
	}
}

class Elli extends AbstractObjectiveFunction {
	Elli() {
		this(1e3);
	}

	Elli(double axisratio) {
		factor = axisratio * axisratio;
	}

	public double factor = 1e6;

	@Override
	public double valueOf(double[] x) {
		double res = 0;
		for (int i = 0; i < x.length; ++i)
			res += Math.pow(factor, (i - 1.) / (x.length - 1.)) * x[i] * x[i]; // TODO
		// verify
		return res;
	}

}

class Rosen extends AbstractObjectiveFunction {
	@Override
	public double valueOf(double[] x) {
		double res = 0;
		for (int i = 0; i < x.length - 1; ++i)
			res += 100 * (x[i] * x[i] - x[i + 1]) * (x[i] * x[i] - x[i + 1]) + (x[i] - 1.) * (x[i] - 1.);
		return res;
	}
}

class Ackley extends AbstractObjectiveFunction {
	double axisratio = 1.;

	Ackley(double axra) {
		axisratio = axra;
	}

	public Ackley() {
	}

	@Override
	public double valueOf(double[] x) {
		double res = 0;
		double res2 = 0;
		double fac = 0;
		for (int i = 0; i < x.length; ++i) {
			fac = Math.pow(axisratio, (i - 1.) / (x.length - 1.));
			res += fac * fac * x[i] * x[i];
			res2 += Math.cos(2. * Math.PI * fac * x[i]);
		}
		return (20. - 20. * Math.exp(-0.2 * Math.sqrt(res / x.length)) + Math.exp(1.) - Math.exp(res2 / x.length));
	}
}

class Rastrigin extends AbstractObjectiveFunction {
	Rastrigin() {
		this(1, 10);
	}

	Rastrigin(double axisratio, double amplitude) {
		this.axisratio = axisratio;
		this.amplitude = amplitude;
	}

	public double axisratio = 1;
	public double amplitude = 10;

	@Override
	public double valueOf(double[] x) {
		double fac;
		double res = 0;
		for (int i = 0; i < x.length; ++i) {
			fac = Math.pow(axisratio, (i - 1.) / (x.length - 1.));
			if (i == 0 && x[i] < 0)
				fac *= 1.;
			res += fac * fac * x[i] * x[i] + amplitude * (1. - Math.cos(2. * Math.PI * fac * x[i]));
		}
		return res;
	}
}

/*
 * Template fitness function class fff extends AbstractObjectiveFunction {
 * public double valueOf(double[] x) { double res = 0; for (int i = 0; i <
 * x.length; ++i) { } return res; } }
 */
